#include "apis.hpp"
#include "pch.hpp"
#include "sts.hpp"
#include "url_regex.hpp"
#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <gtk/gtk.h>
#include <iostream>
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <vector>
#include <vte/vte.h>

#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>

#ifdef GDK_WINDOWING_X11 // X11 ?
# include <gdk/gdkx.h>
#endif
static std::function<void()> reload_config;

int main(int argc, char **argv) {
    std::cout << "terminal_again start running" << std::endl;
    GError *error = nullptr;
    char *directory = nullptr;
    gboolean version = FALSE, hold = FALSE;

    GOptionContext *context = g_option_context_new(nullptr);
    char *role = nullptr, *execute = nullptr, *config_file = nullptr;
    char *title = nullptr, *icon = nullptr;
    bool show_scrollbar = false;

    GOptionEntry const entries[] = {
        {"version", 'v', 0, G_OPTION_ARG_NONE, &version, "Version info",
         nullptr},
        {"exec", 'e', 0, G_OPTION_ARG_STRING, &execute, "Command to execute",
         "COMMAND"},
        {"role", 'r', 0, G_OPTION_ARG_STRING, &role, "The role to use", "ROLE"},
        {"title", 't', 0, G_OPTION_ARG_STRING, &title, "Window title", "TITLE"},
        {"directory", 'd', 0, G_OPTION_ARG_STRING, &directory,
         "Change to directory", "DIRECTORY"},
        {"hold", 0, 0, G_OPTION_ARG_NONE, &hold,
         "Remain open after child process exits", nullptr},
        {"config", 'c', 0, G_OPTION_ARG_STRING, &config_file,
         "Path of config file", "CONFIG"},
        {"icon", 'i', 0, G_OPTION_ARG_STRING, &icon, "Icon", "ICON"},
        {nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr}};
    g_option_context_add_main_entries(context, entries, nullptr);
    g_option_context_add_group(context, gtk_get_option_group(TRUE));

    if (!g_option_context_parse(context, &argc, &argv, &error)) {
        g_printerr("option parsing failed: %s\n", error->message);
        g_clear_error(&error);
        return EXIT_FAILURE;
    }

    g_option_context_free(context);

    if (version) {
        // TODO:
        g_print("vte %u.%u.%u %s\n", 0, 0, 1, "fuck TODO");
        return EXIT_SUCCESS;
    }

    if (directory) {
        if (chdir(directory) == -1) {
            perror("chdir");
            return EXIT_FAILURE;
        }
        g_free(directory);
    }

    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    GtkWidget *panel_overlay = gtk_overlay_new();
    GtkWidget *hint_overlay = gtk_overlay_new();

    GtkWidget *vte_widget = vte_terminal_new();
    VteTerminal *vte = VTE_TERMINAL(vte_widget);
    vte_terminal_set_xalign(vte, VTE_ALIGN_CENTER);
    vte_terminal_set_yalign(vte, VTE_ALIGN_CENTER);

    GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_style_context_add_class(gtk_widget_get_style_context(hbox),
                                "Terminal-Again");
    GtkWidget *scrollbar = gtk_scrollbar_new(
        GTK_ORIENTATION_VERTICAL,
        gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(vte_widget)));
    gtk_box_pack_start(GTK_BOX(hbox), hint_overlay, TRUE, TRUE, 0);
    gtk_box_pack_start(GTK_BOX(hbox), scrollbar, FALSE, FALSE, 0);

    if (role) {
        gtk_window_set_role(GTK_WINDOW(window), role);
        g_free(role);
    }

    char **command_argv;
    char *default_argv[2] = {nullptr, nullptr};

    if (execute) {
        int argcp;
        char **argvp;
        g_shell_parse_argv(execute, &argcp, &argvp, &error);
        if (error) {
            g_printerr("failed to parse command: %s\n", error->message);
            return EXIT_FAILURE;
        }
        command_argv = argvp;
    } else {
        default_argv[0] = get_user_shell_with_fallback();
        command_argv = default_argv;
    }
    KeybindInfo info{
        GTK_WINDOW(window),      vte, SearchPanelInfo{}, SelectInfo{},
        ConfigInfo{config_file},
    };
    load_config(GTK_WINDOW(window), vte, scrollbar, hbox, &info.config,
                icon ? nullptr : &icon, &show_scrollbar);

    reload_config = [&] {
        load_config(GTK_WINDOW(window), vte, scrollbar, hbox, &info.config,
                    nullptr, nullptr);
    };
    signal(SIGUSR1, [](int) { reload_config(); });

    GdkRGBA transparent{0, 0, 0, 0};

    override_background_color(hint_overlay, &transparent);
    override_background_color(info.panel.da, &transparent);

    gtk_widget_set_halign(info.panel.da, GTK_ALIGN_FILL);
    gtk_widget_set_valign(info.panel.da, GTK_ALIGN_FILL);
    gtk_overlay_add_overlay(GTK_OVERLAY(hint_overlay), info.panel.da);

    gtk_widget_set_margin_start(info.panel.entry, 5);
    gtk_widget_set_margin_end(info.panel.entry, 5);
    gtk_widget_set_margin_top(info.panel.entry, 5);
    gtk_widget_set_margin_bottom(info.panel.entry, 5);
    gtk_overlay_add_overlay(GTK_OVERLAY(panel_overlay), info.panel.entry);

    gtk_widget_set_halign(info.panel.entry, GTK_ALIGN_START);
    gtk_widget_set_valign(info.panel.entry, GTK_ALIGN_END);

    gtk_container_add(GTK_CONTAINER(panel_overlay), hbox);
    gtk_container_add(GTK_CONTAINER(hint_overlay), vte_widget);
    gtk_container_add(GTK_CONTAINER(window), panel_overlay);

    GtkTargetEntry const drag_entry = {const_cast<char *>("text/plain"), 0, 0};
    gtk_drag_dest_set(window, GTK_DEST_DEFAULT_ALL, &drag_entry, 1,
                      GDK_ACTION_COPY);
    g_signal_connect(window, "drag-data-received",
                     G_CALLBACK(drag_data_received_cb), info.vte);

    if (!hold) {
        g_signal_connect(vte, "child-exited", G_CALLBACK(exit_with_status),
                         nullptr);
    }
    g_signal_connect(window, "destroy", G_CALLBACK(exit_with_success), nullptr);
    g_signal_connect(vte, "key-press-event", G_CALLBACK(key_press_cb), &info);
    g_signal_connect(info.panel.entry, "key-press-event",
                     G_CALLBACK(entry_key_press_cb), &info);
    g_signal_connect(panel_overlay, "get-child-position",
                     G_CALLBACK(position_overlay_cb), nullptr);
    g_signal_connect(vte, "button-press-event", G_CALLBACK(button_press_cb),
                     &info.config);
    g_signal_connect(vte, "bell", G_CALLBACK(bell_cb),
                     &info.config.urgent_on_bell);
    DrawCallbackInfo draw_cb_info{vte, &info.panel, &info.config.hints,
                                  info.config.filter_unmatched_urls};
    g_signal_connect_swapped(info.panel.da, "draw", G_CALLBACK(draw_cb),
                             &draw_cb_info);

    g_signal_connect(window, "focus-in-event", G_CALLBACK(focus_cb), nullptr);
    g_signal_connect(window, "focus-out-event", G_CALLBACK(focus_cb), nullptr);

    on_alpha_screen_changed(GTK_WINDOW(window), nullptr, nullptr);
    g_signal_connect(window, "screen-changed",
                     G_CALLBACK(on_alpha_screen_changed), nullptr);

    g_signal_connect_swapped(
        window, "enter-notify-event",
        G_CALLBACK(+[](KeybindInfo *info, GdkEventCrossing *event) -> gboolean {
            if (info->config.dpi_aware) {
                adjust_font_size(info->vte, event->window,
                                 info->config.font_size);
            }
            return FALSE;
        }),
        &info);

    g_signal_connect_swapped(
        window, "configure-event",
        G_CALLBACK(+[](KeybindInfo *info, GdkEventConfigure *event) {
            if (info->config.dpi_aware) {
                adjust_font_size(info->vte, event->window,
                                 info->config.font_size);
            }
        }),
        &info);

    g_signal_connect_swapped(
        vte, "selection-changed", G_CALLBACK(+[](KeybindInfo *info) {
            if (info->config.copy_mouse_selection &&
                info->select.mode == Vi_Mode::V_M_Insert &&
                vte_terminal_get_has_selection(info->vte)) {
                vte_terminal_copy_clipboard_format(info->vte, VTE_FORMAT_TEXT);
            }
        }),
        &info);

    if (info.config.fullscreen) {
        g_signal_connect(window, "window-state-event",
                         G_CALLBACK(window_state_cb), &info);
    }

    if (title) {
        info.config.dynamic_title = FALSE;
        gtk_window_set_title(GTK_WINDOW(window), title);
        g_free(title);
    } else {
        g_signal_connect(vte, "window-title-changed",
                         G_CALLBACK(window_title_cb),
                         &info.config.dynamic_title);
        if (execute) {
            gtk_window_set_title(GTK_WINDOW(window), execute);
        } else {
            window_title_cb(vte, &info.config.dynamic_title);
        }
    }

    if (icon) {
        gtk_window_set_icon_name(GTK_WINDOW(window), icon);
        g_free(icon);
    }

    gtk_widget_grab_focus(vte_widget);
    gtk_widget_show_all(window);
    gtk_widget_hide(info.panel.entry);
    gtk_widget_hide(info.panel.da);
    if (!show_scrollbar) {
        gtk_widget_hide(scrollbar);
    }

    char **env = g_get_environ();

#ifdef GDK_WINDOWING_X11
    if (GDK_IS_X11_SCREEN(gtk_widget_get_screen(window))) {
        GdkWindow *gdk_window = gtk_widget_get_window(window);
        if (!gdk_window) {
            g_printerr("no window\n");
            return EXIT_FAILURE;
        }
        char xid_s[std::numeric_limits<long unsigned>::digits10 + 1];
        snprintf(xid_s, sizeof(xid_s), "%lu", GDK_WINDOW_XID(gdk_window));
        env = g_environ_setenv(env, "WINDOWID", xid_s, TRUE);
    }
#endif

    // Let VTE pick a reasonable value.
    env = g_environ_unsetenv(env, "TERM");

    vte_terminal_spawn_async(vte, VTE_PTY_DEFAULT, nullptr, command_argv, env,
                             G_SPAWN_SEARCH_PATH, nullptr, nullptr, nullptr, -1,
                             nullptr, spawn_callback, nullptr);

    int width, height, padding_left, padding_top, padding_right, padding_bottom;
    long const char_width = vte_terminal_get_char_width(vte);
    long const char_height = vte_terminal_get_char_height(vte);

    gtk_window_get_size(GTK_WINDOW(window), &width, &height);
    get_vte_padding(vte, &padding_left, &padding_top, &padding_right,
                    &padding_bottom);
    vte_terminal_set_size(
        vte, (width - padding_left - padding_right) / char_width,
        (height - padding_top - padding_bottom) / char_height);

    g_strfreev(env);

    gtk_main();
    return EXIT_FAILURE; // child process did not cause termination

    return 0;
}
